package vip.ola.manage.secruity;

import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import vip.ola.core.common.constant.MchConstant;
import vip.ola.core.common.util.CookieUtil;
import vip.ola.core.common.util.MyLog;
import vip.ola.manage.common.config.JwtConfig;
import vip.ola.manage.utils.SpringUtil;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

import static vip.ola.core.common.constant.CacheKey.MGR_TOKEN;
import static vip.ola.core.common.constant.CacheKey.TOKEN_TIMEOUT;

@SuppressWarnings("SpringJavaAutowiringInspection")
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    private static final MyLog _log = MyLog.getLog(JwtAuthenticationTokenFilter.class);

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {

        //获取authToken参数
        String authToken = StringUtils.defaultIfEmpty( request.getParameter("access_token"),
                                                       CookieUtil.getCookieByName(request, getJwtConfigBean().getCookie()));

        //如果token为空
        if(StringUtils.isEmpty(authToken)){
            chain.doFilter(request, response); return ;
        }

        String username = getJwtTokenUtilBean().getUsernameFromToken(authToken);

        //token字符串解析失败 || redis不存在（token已失效）
        if( StringUtils.isEmpty(username) ||
                StringUtils.isEmpty(getStringRedisTemplate().opsForValue().get(MGR_TOKEN + authToken))
        ){
            chain.doFilter(request, response); return ;
        }

        _log.debug("checking authentication {}", username);
        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            // 如果我们足够相信token中的数据，也就是我们足够相信签名token的secret的机制足够好
            // 这种情况下，我们可以不用再查询数据库，而直接采用token中的数据
            // 我们还是通过Spring Security的 @UserDetailsService 进行了数据查询
            // 但简单验证的话，你可以采用直接验证token是否合法来避免昂贵的数据查询
            UserDetails userDetails = getUserDetailsServiceBean().loadUserByUsername(username);
            if (userDetails != null && getJwtTokenUtilBean().validateToken(authToken, userDetails)) {
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                _log.debug("authenticated user " + username + ", setting security context");
                SecurityContextHolder.getContext().setAuthentication(authentication);

                //redis put authToken
                getStringRedisTemplate().opsForValue().set(MGR_TOKEN + authToken, "1",
                        TOKEN_TIMEOUT, TimeUnit.SECONDS);
            }else{
                getStringRedisTemplate().delete(MGR_TOKEN + authToken);
                chain.doFilter(request, response); return ; //数据库查询失败，或用户名/密码有修改，需重新 签token
            }
        }

        chain.doFilter(request, response);
    }


    private static JwtTokenUtil getJwtTokenUtilBean(){
        return SpringUtil.getBean(JwtTokenUtil.class);
    }

    private static UserDetailsService getUserDetailsServiceBean(){
        return SpringUtil.getBean(UserDetailsService.class);
    }

    private static StringRedisTemplate getStringRedisTemplate(){
        return SpringUtil.getBean(StringRedisTemplate.class);
    }

    private static JwtConfig getJwtConfigBean(){
        return SpringUtil.getBean(JwtConfig.class);
    }

}
